package cn.codeartist.springboot.component.http.converter;

import cn.codeartist.springboot.component.http.bean.RequestWrapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.MimeType;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Optional;

/**
 * 表单转换器
 * - 只提供写，用于RestTemplate提交表单
 * - 支持Content-Type：application/x-www-form-urlencoded
 * - 默认 UTF-8 编码
 *
 * @author 艾江南
 * @date 2019/7/11
 */
public class FormHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    public FormHttpMessageConverter() {
        super(DEFAULT_CHARSET, MediaType.APPLICATION_FORM_URLENCODED,
                new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET));
    }

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        // 表单提交的参数不能直接是数组或集合，因为表单参数必须存在 键 和 值
        return !clazz.isArray() && !Collection.class.isAssignableFrom(clazz);
    }

    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException, IOException {
        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        byte[] bytes = RequestWrapper.serializeParam(o, true).getBytes(charset);
        outputMessage.getHeaders().setContentLength(bytes.length);
        StreamUtils.copy(bytes, outputMessage.getBody());
    }

    private Charset getContentTypeCharset(MediaType contentType) {
        return Optional.ofNullable(contentType).map(MimeType::getCharset).orElseGet(this::getDefaultCharset);
    }
}
